#pragma once

#include "builders.h"
#include "display.h"

namespace sfc::fmt {

template <class W>
class Formatter {
  W& _out;
  Style _style = {};

 public:
  [[sfc_inline]] Formatter(W& out) : _out{out} {}

  [[sfc_inline]] auto style() const -> const Style& {
    return _style;
  }

  [[sfc_inline]] void set_style(const Style& s) {
    _style = s;
  }

  [[sfc_inline]] void write_str(str::Str s) {
    _out.write_str(s);
  }

  [[sfc_inline]] void write_chr(char c) {
    _out.write_str(str::Str{&c, 1});
  }

  [[sfc_inline]] void write_chs(char c, usize n) {
    char buf[8] = {c, c, c, c, c, c, c, c};
    for (; n != 0;) {
      const auto len = cmp::min(n, 8UL);
      _out.write_str(str::Str{buf, len});
      n -= len;
    }
  }

  void pad(str::Str s) {
    const auto width = _style.width();

    // check first
    if (width <= s.len()) {
      this->write_str(s);
      return;
    }

    const auto fill = _style.fill();
    const auto npad = width - s.len();

    switch (_style.align()) {
      default:
      case '<':
        _out.write_str(s);
        this->write_chs(fill, npad);
        break;
      case '>':
        this->write_chs(fill, npad);
        _out.write_str(s);
        break;
      case '=':
      case '^':
        this->write_chs(fill, (npad + 0) / 2);
        _out.write_str(s);
        this->write_chs(fill, (npad + 1) / 2);
        break;
    }
  }

  void pad_num(bool is_neg, str::Str body) {
    const auto width = _style.width();
    const auto sign = _style.sign(is_neg);
    const auto fill = _style.fill();
    const auto align = fill == '0' ? '=' : _style.align();

    const auto npad = num::saturating_sub<usize>(width, sign.len() + body.len());

    switch (align) {
      default:
      case '<':
        this->write_str(sign);
        this->write_str(body);
        this->write_chs(fill, npad);
        break;
      case '>':
        this->write_chs(fill, npad);
        this->write_str(sign);
        this->write_str(body);
        break;
      case '=':
        this->write_str(sign);
        this->write_chs(fill, npad);
        this->write_str(body);
        break;
      case '^':
        this->write_chs(fill, (npad + 0) / 2);
        this->write_str(sign);
        this->write_str(body);
        this->write_chs(fill, (npad + 1) / 2);
    }
  }

  void write(const auto& val) {
    if constexpr (requires { val.fmt(*this); }) {
      val.fmt(*this);
    } else {
      Display{val}.fmt(*this);
    }
  }

  void write_fmt(str::Str fmts, const auto&... args) {
    Args{fmts, args...}.fmt(*this);
  }

  auto debug_tuple() -> DebugTuple<Formatter> {
    return DebugTuple{*this};
  }

  auto debug_list() -> DebugList<Formatter> {
    return DebugList{*this};
  }

  auto debug_set() -> DebugSet<Formatter> {
    return DebugSet{*this};
  }

  auto debug_map() -> DebugMap<Formatter> {
    return DebugMap{*this};
  }

  auto debug_struct() -> DebugStruct<Formatter> {
    return DebugStruct{*this};
  }
};

void write(auto& out, str::Str fmts, const auto&... args) {
  Formatter{out}.write_fmt(fmts, args...);
}

}  // namespace sfc::fmt
